module Pan

  class Session 
    attr_reader :inspectors
    attr_accessor :session_id, :show_lines, :show_levels, :application
    def initialize(app)
      @application = app
      @callbacks = Callbacks.new
      @expired = false
      @session_id = nil
      @show_levels = 10
      @show_lines = 10
      @backtracker = Backtracker.new
      @inspectors = false
      touch
    end 

    # inspectors

    def toggle_inspectors
      @inspectors = !@inspectors
    end

    # hooks

    def pre_render; end
    def post_render; end
    def pre_callbacks; end
    def post_callbacks; end
    
    def init
      @callbacks = Callbacks.new
    end

    def redirect_to(url)
      res.redirect_302(url)
      # @escape.call(nil)
      throw :escape_request_processing
    end
    
    def current_view_id
      Dynamic.current_viewid
    end

    def req
      Dynamic.current_req
    end

    def res
      Dynamic.current_res
    end
    
    ### Reload

    def self.reload
      Dir["{app,lib}/**/*.rb"].each do |file|
        load(file)
      end
      load('session.rb')
      # reloading all files fails
      #$".each do |file|
      #  load file
      #end
    end

    def reload_application
      self.class.reload
    end
    
    ### Serialization
    
    ## Marshal
    
    def marshal_load(arr)
      @session_id,@root,@expired = arr
      @callbacks = Callbacks.new
      @show_levels = 10
      @show_lines = 10
    end
    
    def marshal_dump
      [@session_id, @root, @expired]
    end
    
    ## YAML
    
    def to_yaml_properties
      ["@session_id","@root"]
    end
    
    ### API
    def base_url
      #FIX: this is a bit fishy
      req.base_url
    end

    def application_url
      req.application_url
    end
    
    def root
      @root
    end

    def root_push(obj)
      @root.push(obj)
    end
    
    def files_for_reload
      []
    end
    
    def expired?
      @expired
    end
    
    def expire
      @expired = true
    end
    
    def render_root(html)
      root.render(html)
    end
    
    ## Backtracking
    
    def snapshot_all(view_id)
      @backtracker.snapshot_all(view_id)
    end
    
    def register_object_for_backtracking(obj)
      @backtracker.register_object_for_backtracking(obj)
    end
    
    def is_backtracked?(obj)
      @backtracker.is_backtracked?(obj)
    end
    
    ## invoking callbacks
    
    def invoke_callbacks
      begin
        req.callbacks.each do |name, val|
          invoke_callback(name,val)
        end
      rescue Exception => e
        @root.push(Backtrace.new(e, self))
      end
    end
    
    def invoke_callback(key, value)
      if callback = @callbacks.find(key)
        catch :call_escape_tag do
          callback.invoke(self, value)
        end
        yield if block_given?
      end
    end
    
    def sorted_params(params)
      params.sort{|a,b| a.first <=> b.first}
    end
    
    def params
      req.params
    end
    
    def param_values
      sorted_params(req.params).map{|key,val| val}
    end
    
    ### View ID
    
    def new_view_id
      # sprintf("%08x", rand(2**32)).upcase
      if @vc != nil
        @vc += 1
      else
        @vc = 0
      end
      sprintf("%04X", @vc)
    end
    
    def view_id
      if req.vid
        req.vid  
      else
        raise Exception.new("no view_id provided")
      end
    end
    
    def touch
      @last_touched = Time.now
    end

    def staleness
      Time.now - @last_touched  # time since touched in seconds
    end

    ## main entry point
    
    def handle(req, res)
      touch
      catch :escape_request_processing do
        # @escape = escape
        Dynamic.let(:current_req => req, :current_res => res) {
          vid = view_id()
          begin
            Dynamic.let(:current_session => self, :current_viewid => vid) {
              if req.cb
                pre_callbacks
                handle_callback_request()
                post_callbacks
              else
                pre_render
                handle_render_request()
                post_render
              end
            }
          rescue Errno::EPIPE
            res.close
          rescue Exception => e
            puts e
            puts e.backtrace
            @root.push(Backtrace.new(e,self))
            Dynamic.current_res.redirect_302("#{req.application_url}/#{@session_id}#{vid}")
          end
        }
      end
    end
    
    ## Callback
    
    def handle_callback_request
      Pan::log("Callback #{@session_id} #{Dynamic.current_viewid}")
      @backtracker.restore_all(Dynamic.current_viewid)
      nview_id = new_view_id()
      invoke_callbacks()
      snapshot_all(nview_id)
      Dynamic.current_req.path = "/app"
      root.callbacks_done([self])
      if res.not_rendered?
        req.finalize_new_path
        # TODO change to redirect_to(:sid=>@session_id ...)
        res.redirect_302("#{req.application_url}/#{@session_id}#{nview_id}")
      end
    end
    
    ## Render
    
    def handle_render_request
      Pan::log("Render #{@session_id} #{Dynamic.current_viewid}")
      @backtracker.restore_all(Dynamic.current_viewid)
      canvas = HtmlCanvas.new(@callbacks)
      render_root(canvas)
      Dynamic.current_res.html_response(canvas.to_html)
    end
  end
  
end
